package com.wind.test;

import com.wind.entity.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class JpaTest03_映射关系 {

  private EntityManagerFactory entityManagerFactory;
  private EntityManager entityManager;
  private EntityTransaction entityTransaction;

  @Before
  public void init() {
    entityManagerFactory = Persistence.createEntityManagerFactory("JpaDemo");
    entityManager = entityManagerFactory.createEntityManager();
    entityTransaction = entityManager.getTransaction();
    entityTransaction.begin();
  }

  @After
  public void destroy() {
    entityTransaction.commit();
    entityManager.close();
    entityManagerFactory.close();
  }

  /**
   * n --> 1
   */
  /*
    增：建议保存多对一的时候，先保存 1 的一端，再保存 n 的一端，这样不会多出额外的 UPDATE 语句
   */
  @Test
  public void testManyToOnePersist() {

    User user = new User();
    user.setLastName("U_BBB");

    Role role = new Role();
    role.setRoleName("R_BBB");
    // 设置关联关系
    role.setUser(user);

    //  执行保存操作：
    //    1）先保存 1 的一端，再保存 n 的一端：两条 insert
    //    2）先保存 n 的一端，再保存 1 的一端：两条 insert 一条 update
    entityManager.persist(user);
    entityManager.persist(role);
  }

  /*
    查：默认情况下，JPA 会使用 左外连接 来获取 n 的一端的对象与其关联的 1 的一端的对象
   */
  @Test
  public void testManyToOneFind() {
    Role role = entityManager.find(Role.class, 1);
    System.out.println(role.getRoleName());
    // fetch属性修改为懒加载，要获取 User 的时候才去查~！
    System.out.println(role.getUser().getLastName());
  }

  /*
    删：可以直接删除 n 的一端，不能直接删除 1 的一端，因为有外键约束
   */
  @Test
  public void testManyToOneRemove() {
    // 删除 n 的一端：获取对象、删除。（可以，直接删除~！）
//    Role role = entityManager.find(Role.class, 1);
//    entityManager.remove(role);

    // 如果删除 1 的一端呢？（不行，因为有外键约束~！ ERROR: Cannot delete or update a parent row: a foreign key constraint fails | 错误:不能删除或更新父行:外键约束失败）
    User user = entityManager.find(User.class, 7);
    entityManager.remove(user);
  }

  /*
    改：n 修改 1 一端的值，直接 set 就行~！（我是没想到啊~！！）
   */
  @Test
  public void testManyToOneUpdate() {
    // 先获取再修改
    Role role = entityManager.find(Role.class, 2);
    role.getUser().setLastName("我是被 Role 修改后的值~！");
  }

  /**
   * 1 --> n
   */
  /*
    增：单向 1-->n 关联关系保存是，一定会多出 UPDATE 语句，因为 n 的一端保存时 不会插入外键列
   */
  @Test
  public void testOneToManyPersist() {

    // 创建 要保存的对象
    User user = new User();
    Role role = new Role();
    user.setLastName("变形金刚 zzz");
    role.setRoleName("擎天柱 mmm");
    HashSet<Role> hashSetset = new HashSet<>();
    hashSetset.add(role);
    // 建立 关联关系
    user.setRoles(hashSetset);
    // 保存 操作
    entityManager.persist(user);
    entityManager.persist(role);
  }

  /*
    查：默认使用懒加载的关联策略
   */
  @Test
  public void testOneToManyFind() {
    Role role = entityManager.find(Role.class, 4);
    // 两条 SQL 语句，分两次查询打印的，所以默认使用的是懒加载策略（也可以修改 用 @ManyToOne(fetch = FetchType.EAGER)）
    System.out.println(role.getRoleName());
    System.out.println(role.getUser().getLastName());
  }

  /*
    删：默认情况下，若删除 1 的一端，则会先将 n 的一端的外键置空，然后进行删除。可以修改 @OneToMany(cascade = {CascadeType.REMOVE}) 完成 级联删除
   */
  @Test
  public void testOneToManyRemove() {
    User user = entityManager.find(User.class, 11);
    // 删除 存在外键的数据时候，会先将所有关联外键的值 置空，再删除
    entityManager.remove(user); // User 在 Role 里面是外键
  }

  /*
    改
   */
  @Test
  public void testOneToManyUpdate() {
    User user = entityManager.find(User.class, 7);
    user.getRoles().iterator().next().setRoleName("我是被OneToMany修改的值");
  }

  /**
   *  n <--> 1
   */
  /*
    增：双向 多 对 一
   */
  @Test
  public void testToWayManyToOnePersist() {

    // 准备数据
    Customer customer = new Customer();
    customer.setCustomerName("刘强东");

    Set<Order> orders = new HashSet<>();
    Order order1 = new Order();
    order1.setOrderName("充气娃娃-1");
    Order order2 = new Order();
    order2.setOrderName("充气娃娃-2");
    orders.add(order1);
    orders.add(order2);

    // 建立关联关系
    customer.setOrders(orders);
    order1.setCustomer(customer);
    order2.setCustomer(customer);

    // 执行保存操作（本身就带有事务）
    entityManager.persist(order1);    // n
    entityManager.persist(order2);    // n
    entityManager.persist(customer);  // 1
  }

  /**
   *  1 <--> 1
   */
  /*
    增：双向 1-1 的关联关系, 建议先保存不维护关联关系的一方, 即没有外键的一方, 这样不会多出 UPDATE 语句.
   */
  @Test
  public void testOneToOnePersist() {

    // 准备数据
    Husband husband = new Husband();
    husband.setHusbandName("风");

    Wife wife = new Wife();
    wife.setWifeName("赵");

    // 建立关联关系
    husband.setWife(wife);

    // 执行保存操作
    entityManager.persist(wife);
    entityManager.persist(husband);
  }

  /*
    查：
      Find1：默认情况下, 若获取维护关联关系的一方, 则会通过左外连接获取其关联的对象。但可以通过 @OntToOne 的 fetch 属性来修改加载策略。
      Find2：默认情况下, 若获取不维护关联关系的一方, 则也会通过左外连接获取其关联的对象。
	    可以通过 @OneToOne 的 fetch 属性来修改加载策略. 但依然会再发送 SQL 语句来初始化其关联的对象
	    这说明在不维护关联关系的一方, 不建议修改 fetch 属性。

	  具体原因是：
	    因为在维护关联关系一方 有外键指向关联的对象。
	    而不维护关联关系的一方 没有外键指向关联的对象，所以必须通过发送 SQL 语句来确定是否有关联的对象并查找
   */
  @Test
  public void testOneToOneFind1() {
    Husband husband = entityManager.find(Husband.class, 2);
    System.out.println(husband.getHusbandName());
    System.out.println(husband.getWife().getClass().getName()); // 查看对象的方法：.getClass().getName()
  }

  @Test
  public void testOneToOneFind2() {
    Wife wife = entityManager.find(Wife.class, 2);
    System.out.println(wife.getWifeName());
    System.out.println(wife.getHusband().getClass().getName());
  }

  /**
   *  n -- n
   */
  /*
    增：多对所的保存
   */
  @Test
  public void testManyToManyPersist() {

    // 准备数据
    Teacher teacher1 = new Teacher();
    Teacher teacher2 = new Teacher();
    Student student1 = new Student();
    Student student2 = new Student();

    teacher1.setTeacherName("教师-1");
    teacher2.setTeacherName("教师-2");
    student1.setStudentName("学生-1");
    student2.setStudentName("学生-2");

    // 建立关联关系
    teacher1.getStudents().add(student1);
    teacher1.getStudents().add(student2);
    teacher2.getStudents().add(student1);
    teacher2.getStudents().add(student2);

    student1.getTeachers().add(teacher1);
    student1.getTeachers().add(teacher2);
    student2.getTeachers().add(teacher1);
    student2.getTeachers().add(teacher2);

    // 执行保存操作
    entityManager.persist(teacher1);
    entityManager.persist(teacher2);
    entityManager.persist(student1);
    entityManager.persist(student2);
  }

  /*
    查：对于关联的集合对象, 默认使用懒加载的策略。 使用维护关联关系的一方获取, 还是使用不维护关联关系的一方获取, SQL 语句相同.
   */
  @Test
  public void testManyToManyFind() {
    Teacher teacher = entityManager.find(Teacher.class, 1);
    System.out.println(teacher.getTeacherName());
    Iterator<Student> iterator = teacher.getStudents().iterator();
    // hashNext()：如果还有下个元素
    // next()：取出元素
    while (iterator.hasNext()) {
      System.out.println(iterator.next().getStudentName());
    }
  }

}
